/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {ts, ArkFile} from 'arkanalyzer/lib';
import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger';
import {BaseChecker, BaseMetaData} from '../BaseChecker';
import {MatcherCallback, MatcherTypes} from '../../matcher/Matchers';
import {AstMatcher} from '../../matcher/Matchers';
import {RuleListUtil} from '../../utils/common/DefectsList';
import {Defects, IssueReport} from '../../model/Defects';
import {Rule} from '../../model/Rule';
import {Utils} from '../../utils/common/Utils';
import {RuleFix} from '../../model/Fix';

type Options = [
        'always' | 'never',
    {
        allowNewlines?: boolean;
    }?
];

type RangNum = [charCount: number, newlineCount: number];

const defaultOptions: Options = ['never', {allowNewlines: false}];

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'FuncCallSpacingCheck');

const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: 'docs/func-call-spacing.md',
    description: 'Unexpected whitespace between function name and paren.',
};

export class FuncCallSpacingCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private issueMap: Map<string, IssueReport> = new Map();
    private options: Options = defaultOptions;

    private astMatcher: AstMatcher = {
        matcherType: MatcherTypes.AST,
        match(node: ts.Node): boolean {
            return ts.isCallExpression(node) || ts.isNewExpression(node);
        },
    };

    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.astMatcher,
            callback: this.check,
        };
        return [matchBuildCb];
    }

    public check = (node: ts.CallExpression | ts.NewExpression, arkFile: ArkFile): void => {
        this.parseOptions();
        const sourceFile = node.getSourceFile();

        const children = node.getChildren();
        const chdLength = children.length;

        // 只处理符合条件的节点结构
        if (chdLength !== 4 && chdLength !== 5) {
            return;
        }

        const {nameContent, openParenToken} = this.extractCallExpressionParts(node);
        if (!nameContent || !openParenToken) {
            return;
        }

        const methodName = nameContent.getText();
        const nodeText = node.getText();
        const rangeNum = this.countCharactersAndNewlines(sourceFile, node, nameContent, openParenToken);
        const startPosition = ts.getLineAndCharacterOfPosition(sourceFile, nameContent.getEnd());
        const rangeStart = node.getStart();
        const rangeEnd = node.getEnd();

        this.handleSpacingIssue(
            nodeText,
            rangeStart,
            rangeEnd,
            rangeNum,
            startPosition,
            arkFile.getFilePath(),
            methodName
        );

        this.reportSortedIssues();
    };

    private parseOptions(): void {
        const ruleOptions = (this.rule?.option as Options) || defaultOptions;
        this.options = [
            ruleOptions[0] || defaultOptions[0],
            {...defaultOptions[1], ...ruleOptions[1]},
        ];
    }

    private extractCallExpressionParts(node: ts.Node): {

        nameContent: ts.Node | undefined;
        openParenToken: ts.Node | undefined;
    } {
        const children = node.getChildren();
        const cdLength = children.length;
        const isCallExpr = ts.isCallExpression(node);

        let nameContent: ts.Node | undefined;
        let openParenToken: ts.Node | undefined;

        if (isCallExpr) {
            // 情况 1: 没有 new 关键
            nameContent = children[0];
            openParenToken = children[cdLength - 3];
        } else {
            // 情况 2: 有 new 关键字
            nameContent = children[1];
            openParenToken = children[cdLength - 3];
        }

        return {nameContent, openParenToken};
    }

    private countCharactersAndNewlines(
        sourceFile: ts.SourceFile,
        node: ts.Node,
        nameContent: ts.Node,
        openParenToken: ts.Node
    ): RangNum {
        // 确保范围有效
        const startIndex = ts.getLineAndCharacterOfPosition(sourceFile, nameContent.getEnd());
        const endIndex = ts.getLineAndCharacterOfPosition(sourceFile, openParenToken.getStart());
        const newlineCount = endIndex.line - startIndex.line;

        const fullText = node.getText();
        const nameContentText = nameContent.getText();
        const startDex = fullText.lastIndexOf(nameContentText) + nameContentText.length;

        let charCount = 0;
        if (newlineCount > 0) {
            charCount = newlineCount + endIndex.character;
        } else {
            charCount = endIndex.character - startIndex.character;
        }
        // 统计字符数和换行符数
        const subString = fullText
            .substring(startDex, startDex + charCount)
            .replace(/\?/g, '')
            .replace(/\./g, '');

        return [subString.length, newlineCount];
    }

    private handleSpacingIssue(
        nodeText: string,
        rangeStart: number,
        rangeEnd: number,
        rangeNum: RangNum,
        startPosition: ts.LineAndCharacter,
        filePath: string,
        methodName: string
    ): void {
        const [charCount, newlineCount] = rangeNum;
        const mode = this.options[0];
        const allowNewlines = this.options[1]?.allowNewlines ?? false;

        if (mode === 'never' && charCount > 0) {
            const defect = this.addIssueReport(
                filePath,
                startPosition.line + 1,
                startPosition.character,
                startPosition.character,
                methodName,
                'Unexpected whitespace between function name and paren.'
            );
            //1.修复含有空格
            if (newlineCount === 0) {
                defect.fixable = true;
                this.addFixByText(nodeText, rangeStart, rangeEnd, defect, false);
            } else {
                this.issueMap.set(defect.fixKey, new IssueReport(defect, undefined));
            }
        } else if (mode === 'always' && charCount === 0) {
            const defect = this.addIssueReport(
                filePath,
                startPosition.line + 1,
                startPosition.character + 1,
                startPosition.character + 1,
                methodName,
                'Missing space between function name and paren.'
            );
            this.addFixByText(nodeText, rangeStart, rangeEnd, defect, true);
        } else if (mode === 'always' && !allowNewlines && newlineCount > 0) {
            const defect = this.addIssueReport(
                filePath,
                startPosition.line + 1,
                startPosition.character + 1,
                startPosition.character + 1,
                methodName,
                'Unexpected newline between function name and paren.'
            );
            this.addFixByText(nodeText, rangeStart, rangeEnd, defect, true);
        }
    }

    private addIssueReport(
        arkFilePath: string,
        line: number,
        startCol: number,
        endCol: number,
        name: string,
        message: string
    ): Defects {
        const severity = this.rule.alert ?? this.metaData.severity;
        const defect = new Defects(
            line,
            startCol,
            endCol,
            message,
            severity,
            this.rule.ruleId,
            arkFilePath,
            this.metaData.ruleDocPath,
            true,
            false,
            false
        );
        this.defects.push(defect);
        return defect;
    }

    private formatFnCall(code: string, addSpace: boolean): string {
        if (addSpace) {
            return code.replace(/(\w+)\s*\(\s*/g, '$1 (');
        } else {
            return code.replace(/(\w+)\s*\(\s*/g, '$1(');
        }
    }

    private ruleFix(pos: number, end: number, text: string): RuleFix {
        return {range: [pos, end], text: text};
    }

    private reportSortedIssues(): void {
        if (this.issueMap.size === 0) {
            return;
        }
        const sortedIssues = Array.from(this.issueMap.entries()).sort(([keyA], [keyB]) =>
            Utils.sortByLineAndColumn(keyA, keyB)
        );

        this.issues = [];

        sortedIssues.forEach(([_, issue]) => {
            RuleListUtil.push(issue.defect);
            this.issues.push(issue);
        });
    }

    private addFixByText(
        nodeText: string,
        rangeStart: number,
        rangeEnd: number,
        defect: Defects,
        addSpace: boolean
    ): void {
        const fixText = this.formatFnCall(nodeText, addSpace);
        const fix: RuleFix = this.ruleFix(rangeStart, rangeEnd, fixText);
        this.issueMap.set(defect.fixKey, {defect, fix});
    }
}